// Copyright (C) 2020 Focus Media Holding Ltd. All Rights Reserved.

package cn.pacerx.running.db.mybatis;

import static com.google.common.base.Preconditions.checkState;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Map;

import org.apache.ibatis.ognl.MethodAccessor;
import org.apache.ibatis.ognl.MethodFailedException;
import org.springframework.core.annotation.AnnotatedElementUtils;

import com.google.common.collect.ImmutableMap;

import lombok.SneakyThrows;

/**
 * {@linkplain MethodAccessor} 的一个实现, 注入 {@linkplain MybatisOgnlUtils} 中的工具方法.
 *
 * <p> TODO 支持函数重载 (Overload)
 *
 * @author gongshiwei@focusmedia.cn
 */
class ContextMethodAccessor implements MethodAccessor {
    private static final Map<String, MethodHandle> EXT_UTILS = lookupUtils();

    @Override
    public Object callStaticMethod(Map map, Class clazz, String name, Object[] args) throws MethodFailedException {
        throw new MethodFailedException(clazz, name, new UnsupportedOperationException());
    }

    @Override
    public Object callMethod(Map map, Object target, String name, Object[] args) throws MethodFailedException {
        if (EXT_UTILS.containsKey(name)) {
            MethodHandle methodHandle = EXT_UTILS.get(name);
            try {
                return methodHandle.invokeWithArguments(args);
            } catch (Throwable throwable) {
                throw new MethodFailedException(target, name, throwable);
            }
        }
        throw new MethodFailedException(target, name, new NoSuchMethodException("can't find method '" + name + "', "
                + "available methods are: " + EXT_UTILS.keySet()));
    }

    @SneakyThrows(IllegalAccessException.class)
    private static Map<String, MethodHandle> lookupUtils() {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        ImmutableMap.Builder<String, MethodHandle> utils = ImmutableMap.builder();
        for (Method method : MybatisOgnlUtils.class.getMethods()) {
            OgnlMethod ognlMethod = AnnotatedElementUtils.getMergedAnnotation(method, OgnlMethod.class);
            if (ognlMethod == null) {
                continue;
            }
            checkState(isUtilMethod(method), "Ognl Method must be public and static, but %s not", method);
            MethodHandle methodHandle = lookup.unreflect(method);
            for (String methodName : ognlMethod.value()) {
                utils.put(methodName, methodHandle);
            }
        }
        return utils.build();
    }

    private static boolean isUtilMethod(Method method) {
        int modifiers = method.getModifiers();
        return isPublic(modifiers) && isStatic(modifiers);
    }
}
